package extasysftpserver;

/**
 *
 * @author NSi
 */
import Extasys.DataFrame;
import Extasys.ManualResetEvent;
import Extasys.Network.TCP.Server.Listener.TCPClientConnection;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PassiveServerDTP extends Extasys.Network.TCP.Server.ExtasysTCPServer
{

    private final Object fConnectDisconnectLock = new Object();
    private FTPServer fMyFTPServer;
    public Boolean fIsRunning = false;
    private Charset UTF8_CHARSET = Charset.forName("UTF-8");
    private TCPClientConnection fMyClient;
    // Receive file mode
    private ManualResetEvent fWaitForClientToConnectEvent = new ManualResetEvent(false);
    private TCPClientConnection fClientReceivingFileFrom;
    private final Object fWriteFileLock = new Object();
    private boolean fReceiveFileMode = false;
    private String fReceiveFileName = "";
    private String fReceiveFilePath = "";
    // Send File mode
    private TCPClientConnection fClientSendingFile;
    private boolean fSendFileMode = false;
    private File fFileToSend = null;
    // Send file list mode
    private boolean fSendListFileMode = false;
    private TCPClientConnection fClientSendingFilesList;
    private byte[] fListBytes;

    public PassiveServerDTP(FTPServer myServer, InetAddress host, int port)
    {
        super("", "", 2, 2);
        fMyFTPServer = myServer;

        // Set clients timeout to 2 days
        this.AddListener("", host, port, 5, 8192, 172800000, 5);
        fIsRunning = true;
        fWaitForClientToConnectEvent.Reset();
    }

    @Override
    public void Start() throws IOException, Exception
    {
        fIsRunning = true;
        super.Start();
    }

    @Override
    public void Stop()
    {
        fIsRunning = false;
        super.Stop();
    }

    @Override
    public void OnDataReceive(TCPClientConnection sender, DataFrame data)
    {
        //System.out.println("Data received to Passive Server");
        if (fReceiveFileMode)
        {
            synchronized (fWriteFileLock)
            {
                File f = new File(fReceiveFilePath + File.separator + fReceiveFileName);
                try (FileOutputStream fout = new FileOutputStream(f, true))
                {
                    fout.write(data.getBytes(), 0, data.getLength());
                    fout.close();
                }
                catch (Exception ex)
                {
                }
            }
            //System.out.println(new String(data.getBytes()));
        }
    }

    @Override
    public void OnClientConnect(TCPClientConnection client)
    {
        synchronized (fConnectDisconnectLock)
        {
            fMyClient = client;

            if (fReceiveFileMode)
            {
                // File receive need Synchronous data read
                client.StartReadingIncomingDataSynchronous();
            }

            fWaitForClientToConnectEvent.Set();
        }
    }

    @Override
    public void OnClientDisconnect(TCPClientConnection client)
    {
        synchronized (fConnectDisconnectLock)
        {
            //System.out.println("Client disconnected from Passive server!");
            if (fSendListFileMode)
            {
                fMyFTPServer.ReplyToClientSynchronously(fClientSendingFilesList, "226 Transfer Complete");
            }
            else if (fSendFileMode)
            {
                fMyFTPServer.ReplyToClientSynchronously(fClientSendingFile, "226 Transfer Complete");
            }
            else if (fReceiveFileMode)
            {
                fMyFTPServer.ReplyToClientSynchronously(fClientReceivingFileFrom, "226 Transfer complete");
            }

            this.Stop();
        }
    }

    public void PrepareToSendFilesList(TCPClientConnection sender, ClientProperties props)
    {
        fSendListFileMode = true;
        fClientSendingFilesList = sender;

        // Get files list bytes[]
        try
        {
            File dir = new File(props.fCurrentDirectoryPath);
            if (!dir.exists() || !dir.isDirectory())
            {
                String reply = "550 Directory " + props.fCurrentDirectoryPath + " does not exist";
                fListBytes = reply.getBytes(UTF8_CHARSET);
                return;
            }

            File tmp[];
            if (dir.isDirectory())
            {
                tmp = dir.listFiles();
            }
            else
            {
                tmp = new File[0];
                //tmp[0] = dir;
            }

            String res = "", permission = "";
            for (int i = 0; i < tmp.length; i++)
            {
                try
                {
                    if (tmp[i].canRead())
                    {
                        String str = "";
                        permission = (tmp[i].isDirectory() ? "d" : "-") + "rwxr-xr-x";
                        str += permission + " 1";
                        str += " user group ";
                        str += " " + (tmp[i].isDirectory() ? tmp[i].list().length : tmp[i].length());
                        str += " " + new SimpleDateFormat("M dd hh:mm").format(new Date(tmp[i].lastModified()));
                        str += " " + tmp[i].getName() + "\r\n";
                        res += str;
                    }
                }
                catch (Exception ex)
                {
                    System.err.println("list Error: cannot read file");
                }
            }

            //res += "\r\n";
            if (res.equals(""))
            {
                res = "\r\n";
            }
            fListBytes = res.getBytes(UTF8_CHARSET);
            fMyFTPServer.ReplyToClientSynchronously(sender, "150 Opening ASCII mode data connection for file list");
            SendFilesList();
        }
        catch (Exception e)
        {
            System.err.println("ServerPassiveDTP.PrepareToSendFilesList Error: " + e);
            fMyFTPServer.ReplyToClient(fClientSendingFilesList, "226 Transfer Complete");
            fWaitForClientToConnectEvent.Set();
        }
    }

    private void SendFilesList()
    {
        try
        {
            fWaitForClientToConnectEvent.WaitOne(2500);
            if (fMyClient != null)
            {
                fMyClient.SendDataSynchronous(fListBytes, 0, fListBytes.length);
            }
            else
            {
                fMyFTPServer.ReplyToClientSynchronously(fClientSendingFile, "550 Transfer failed");
            }
        }
        catch (Exception ex)
        {
        }

        if (fMyClient != null)
        {
            fMyClient.DisconnectMe();
        }
    }

    public void PrepareToSendFile(TCPClientConnection sender, File file)
    {
        fClientSendingFile = sender;
        fSendFileMode = true;
        fFileToSend = file;
        fMyFTPServer.ReplyToClient(sender, "150 about to open data connection.");
        SendFile();
    }

    public void SendFile()
    {
        fWaitForClientToConnectEvent.WaitOne(2500);
        if (fMyClient == null)
        {
            fMyFTPServer.ReplyToClientSynchronously(fClientSendingFile, "550 File transfer failed");
            return;
        }

        File file = fFileToSend;

        try
        {
            if (file.isDirectory())
            {
                fMyClient.DisconnectMe();
                return;
            }


            //BufferedInputStream reader = new BufferedInputStream(new FileInputStream(file));
            try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)))
            {
                int counter = 0, chunkSize = 2048 * 10;
                byte buff[] = new byte[chunkSize];

                while ((counter = in.read(buff)) != -1 && fMyClient != null)
                {
                    fMyClient.SendDataSynchronous(buff, 0, counter);
                }
            }




            /*try (FileInputStream in = new FileInputStream(file))
             {
             int counter = 0, chunkSize = 8192;
             byte buff[] = new byte[chunkSize];

             while ((counter = in.read(buff)) != -1 && fMyClient != null)
             {
             fMyClient.SendDataSynchronous(buff, 0, counter);
             }
             }*/
        }
        catch (Exception e)
        {
            System.err.println("PassiveServerDTP.SendFile Error: " + e.getMessage());
            fMyFTPServer.ReplyToClientSynchronously(fClientSendingFile, "550 File transfer failed");
            fMyClient.DisconnectMe();
            return;
        }

        fMyFTPServer.ReplyToClientSynchronously(fClientSendingFile, "226 Transfer Complete");
        fMyClient.DisconnectMe();
    }

    public void PrepareToReceiveFile(TCPClientConnection sender, String fileName)
    {

        if (sender.getTag() != null)
        {
            fileName = fileName.trim();

            fClientReceivingFileFrom = sender;
            fReceiveFileMode = true;
            fReceiveFileName = fileName;

            ClientProperties props = (ClientProperties) sender.getTag();
            fReceiveFilePath = props.fCurrentDirectoryPath;
            sender.setTag(props);

            //System.out.println("PrepareToReceiveFile " + fileName);
            //System.out.println("---- " + props.fCurrentDirectoryPath);

            File f = new File(fReceiveFilePath + File.separator + fileName);
            if (f.exists())
            {
                f.delete();
            }

            f = new File(fReceiveFilePath + File.separator + fileName);
            if (!f.exists())
            {
                try
                {
                    if (!f.createNewFile())
                    {
                        fMyFTPServer.ReplyToClient(sender, "500 Cannot upload file");
                        return;
                    }
                }
                catch (Exception ex)
                {
                    fMyFTPServer.ReplyToClient(sender, "500 Cannot upload file");
                    return;
                }
            }

            fMyFTPServer.ReplyToClient(sender, "150 about to open data connection.");
        }
    }
}